[ 筆記 ] JavaScript 進階 08 - new、extends、super、封裝


Posted by krebikshaw on 2020-09-28

在了解了 Prototype Chain 之後,我們要來利用物件導向的概念,近一步了解在 ES5、 ES6 之間實作的差異,以及 new、super、封裝 的意義是什麼。

new 關鍵字 : 模擬 new 在背後幫你做的事

上一篇文章分享過要建立一個新的物件來繼承另一個資源的方法時,需要用到 new 這個關鍵字,現在我們要來探討,當我們使用 new 時,背後系統幫我們做了什麼

接下來以一個自己寫的 function newDog('yello'),來模擬 new Dog('yello') 在做的事:

const yello = newDog('yello');

white.sayHi();
yello.sayHi();

首先目的是通過自己寫的 newDog 產生一個 instance yello,希望這個 instance 會有 跟用 new 出來的 instance white 有一樣的屬性跟方法。

所以目標放在如何寫出 newDog() 來模擬 new 這個關鍵字背後做的事情。

function newDog(name) {
  const obj = {};                 // 1.
  Dog.call(obj, name);            // 2.
  obj.__proto__ = Dog.prototype;  // 3.
  return obj;                     // 4.
}
  • new 背後做的事:
    1. 產生一個新 object => obj
    2. 把 Dog 當作 constructor,將 this 指向 obj,同時把參數 name 丟進去
      => 因為 Dog 只有一個參數所以用 call,如果是兩個以上的參數就可以用 apply 放陣列
    3. obj.__proto__ 指向 Dog.prototype
    4. 回傳 obj

在 ES5 時,通常是把 function 當成 constructor 來用,稱為「 建構函式 」,而共用的 method 則會放在 prototype 新增,可以避免在每個 instance 重複新增內容一樣的 method。

而關鍵在於 new 關鍵字,用 new 出來的 function, JS 會在背後幫你做好一連串的機制。

接下來我們來看,到了 ES6 之後,寫法上面會變成什麼樣子

ES5 VS ES6

ES5 : 建構函式 + prototype

function Dog(name) {
  this.name = name;
}

Dog.prototype.sayHello = function() {
  console.log(this.name, ' Hello');
}

const happy = new Dog('happy');
happy.sayHello();

const white = new Dog('white');
white.sayHello();

ES6 : Class

class Dog {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log(this.name, ' Hello');
  }
}
const happy = new Dog('happy');
happy.sayHello();

const white = new Dog('white');
white.sayHello();

在 ES6 之後,開始有了 Class 這個語法糖可以使用。當我們想要建立一個物件導向的資源時,感覺有點像宣告一個 class 的物件,把所有跟這個物件相關的 property & method 通通包在一起的感覺。

如此一來只要是在 class 之中宣告的 function 都會被加入 prototype。

extends 繼承

當我們建立好ㄧ個 class 之後,可以以這個 class 為基礎,新建一個類似的 class。就像我想要以「狗」這個類別為基礎,新建一個類別叫「柴犬」,它可以繼承「狗」的屬性及方法。

而這個新建語法就是 extends

  • class 柴犬 extends 狗
class Dog {
  ...
}

class TaiwansDog extends Dog {
  ...
}

super

在 ES6 使用繼承時,如果要將 子類別的 constructor 改寫,需要先呼叫 super() 這個函式,否則會跳出錯誤:Must call super constructor in derived class before accessing 'this' or returning from derived constructor。

class Dog {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    console.log(this.name, 'Hi');
  }
}

class TaiwansDog extends Dog {
  constructor(name) {
    super(name); // => 記得要先 super()
    this.sayHi();
  }
}

const white = new Dog('white');
const black = new TaiwansDog('black');

封裝

先前在 Closure 的概念當中有提到,如果你希望一個變數是不能被任意賦值的,可以將它封裝起來,使外部無法任意存取。

  • ES5 - function 模擬 Class 版 ( real private )
function Wallet(init) {
  let money = init;

  this.getMoney = function() {
    return money;
  }
};

const wallet = new Wallet(100);
console.log(wallet.getMoney());
console.log(wallet.money); // => 存取不到,真正的 private
  • ES6 - constructor 版 ( real private )

一般在 class 裡面初始化變數,會在 constructor 裡面用 this.variable = variable 的寫法,用這個方式宣告出來的變數,是能夠從外部存取的

class Wallet {
  constructor(num) {
    this.money = num;
  }
};

const wallet = new Wallet(100);
console.log(wallet.money)  // 100

但是若是在 constructor 裡面用 varlet 或者 const 宣告變數,只要在 constructor 外部就通通存取不到了,所以需要存取 private property 的 function 只能一並寫在 constructor 裡面。

  • private property 在命名的時候習慣性以 _ 開頭
class Wallet {
  constructor(num) {
    var _money = num;
    this.getMoney = () => _money;
    this.setMoney = (newNum) => _money = newNum;
  }
  getMoney = () => _monsy;  // 像這樣寫在 constructor 外面的 function 就存取不到 _money 了
};

const wallet = new Wallet(100);
console.log(wallet.getMoney());
console.log(wallet._money); // => 存取不到,真正的 private

現在 ES6 還沒有看到通用的方法,最簡單的方法其實可以再 private property or method 的名稱加上 _ 前綴,同事們可以很簡單看出這是個私有屬性,沒事不要去動它。


#OOP #new #super #extends







Related Posts

【筆記】Sass 使用簡介與常用語法

【筆記】Sass 使用簡介與常用語法

Linkedin Java 檢定題庫  try catch 流程

Linkedin Java 檢定題庫 try catch 流程

Jump Diffusion Option Valuation in  Discrete Time

Jump Diffusion Option Valuation in Discrete Time


Comments